//
//  FeedParseOperation.m
//  Feed Rider
//
//  Created by Adrian-Sergiu Nicuta on 08/10/11.
//  Copyright 2011 Adrisen Technologies SPRL. All rights reserved.
//

#import "FeedParseOperation.h"
#import "Feed_RiderAppDelegate.h"
#import "Item.h"
#import "GTMNSString+HTML.h"

NSString *kAddFeedItemsNotif = @"AddEarthquakesNotif";
NSString *kFeedResultsKey = @"EarthquakeResultsKey";
NSString *kFeedItemsErrorNotif = @"EarthquakeErrorNotif";
NSString *kFeedItemsMsgErrorKey = @"EarthquakesMsgErrorKey";

@implementation FeedParseOperation

static const const NSUInteger kMaximumNumberOfEarthquakesToParse = 100;
static NSUInteger const kSizeOfEarthquakeBatch = 50;

static NSString * const kEntryElementName = @"entry";
static NSString * const kItemElementName = @"item";
static NSString * const kLinkElementName = @"link";
static NSString * const kTitleElementName = @"title";
static NSString * const kUpdatedElementName = @"updated";
static NSString * const kDescriptionElementName = @"description";
static NSString * const kSummaryElementName = @"summary";
static NSString * const kContentElementName = @"content";
static NSString * const kPubDateElementName = @"pubDate";
static NSString * const kDateElementName = @"dc:date";
static NSString * const kGuidElementName = @"guid";
static NSString * const kIdElementName = @"id";

@synthesize currentFeedItem;
@synthesize feedData;
@synthesize currentFeedId;
@synthesize currentParseBatch;
@synthesize currentParsedCharacterData;
@synthesize oldDate;

- (id)initWithData:(NSData *)data
{
    self = [super init];
    if (self) {
        feedData = [data copy];

        dateFormatter1 = [[NSDateFormatter alloc] init];
        [dateFormatter1 setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
        [dateFormatter1 setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
        [dateFormatter1 setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];

        dateFormatter2 = [[NSDateFormatter alloc] init];
        [dateFormatter2 setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
        [dateFormatter2 setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
        [dateFormatter2 setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss ZZZZ"];
    }
    
    return self;
}

-(void)saveCurrentBatch
{
    NSError *error = nil;

    NSMutableArray *newItems = [NSMutableArray array];
    
    for (TempItem *item in self.currentParseBatch) {
        if (currentFeed == nil) {
            currentFeed = (Feed *)[managedObjectContext objectWithID:item.feedId];
        }

        NSFetchRequest *req = [[NSFetchRequest alloc] initWithEntityName:@"Item"];
        
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(guid = %@) AND (feed = %@)", item.guid, item.feedId];
        req.predicate = predicate;
        
        NSArray *results = [managedObjectContext executeFetchRequest:req error:&error];
        [req release];
        
        if (results.count == 0) {
            [newItems addObject:item];
            NSNumber *newCount = [NSNumber numberWithInt:currentFeed.unreadCount.intValue + 1];
            currentFeed.unreadCount = newCount;
        }
    }

    for (TempItem *item in newItems) {
        Item *newItem = (Item *)[NSEntityDescription insertNewObjectForEntityForName:@"Item" inManagedObjectContext:managedObjectContext];
                
        NSMutableString *summary = nil;
        if (item.Description) {
            summary = [[[NSMutableString alloc] initWithString:[item.Description gtm_stringByUnescapingFromHTML]] autorelease];
        } else {
            summary = [NSMutableString string];
        }
        [regex replaceMatchesInString:summary options:0 range:NSMakeRange(0, summary.length) withTemplate:@""];
        
        newItem.guid = item.guid;
        newItem.title = item.Title;
        newItem.summaryHTML = item.Description;
        newItem.summary = summary;
        newItem.updated = item.updated;
        newItem.link = item.Link;
        newItem.feed = currentFeed;
    }

    if(![managedObjectContext save:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    
    [[NSNotificationCenter defaultCenter] postNotificationName:NSManagedObjectContextDidSaveNotification object:self];
}

-(void)main 
{    
    self.currentParseBatch = [NSMutableArray array];
    self.currentParsedCharacterData = [NSMutableString string];
    
    NSDate *today = [NSDate date];
    NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
    [dateComponents setMonth:-6];
    oldDate = [gregorian dateByAddingComponents:dateComponents toDate:today options:0];

    [gregorian release];
    [dateComponents release];
    
    NSError *error = nil;

    regex = [NSRegularExpression alloc];
    [regex initWithPattern:@"<[^>]*>" options:NSRegularExpressionCaseInsensitive error:&error];

    managedObjectContext = [[NSManagedObjectContext alloc] init];
    Feed_RiderAppDelegate *app = [[UIApplication sharedApplication] delegate];
    managedObjectContext.persistentStoreCoordinator = app.persistentStoreCoordinator;
    
    currentFeed = (Feed *)[managedObjectContext objectWithID:currentFeedId];
    
    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:self.feedData];
    [parser setDelegate:self];
    [parser parse];
    
    if ([self.currentParseBatch count] > 0) {
        [self saveCurrentBatch];
    }
    
    [managedObjectContext release];
    [regex release];
    
    self.currentParseBatch = nil;
    self.currentParsedCharacterData = nil;
    
    [parser release];
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
                                        namespaceURI:(NSString *)namespaceURI
                                       qualifiedName:(NSString *)qName
                                          attributes:(NSDictionary *)attributeDict {
    // If the number of parsed earthquakes is greater than
    // kMaximumNumberOfEarthquakesToParse, abort the parse.
    //
    if (parsedItemsCounter >= kMaximumNumberOfEarthquakesToParse) {
        // Use the flag didAbortParsing to distinguish between this deliberate stop
        // and other parser errors.
        //
        didAbortParsing = YES;
        [parser abortParsing];
    }
    if ([elementName isEqualToString:kEntryElementName] || [elementName isEqualToString:kItemElementName]) {
        currentFeedItem = [[TempItem alloc] init];
        currentFeedItem.Updated = [NSDate date];
        currentFeedItem.feedId = currentFeedId;
    } else if ([elementName isEqualToString:kLinkElementName]) {
        NSString *relAttribute = [attributeDict valueForKey:@"rel"];
        if ([relAttribute isEqualToString:@"alternate"]) {
            NSString *WebLink = [attributeDict valueForKey:@"href"];
            self.currentFeedItem.Link = WebLink;
        }
    } 
    if ([elementName isEqualToString:kTitleElementName] ||
        [elementName isEqualToString:kUpdatedElementName] ||
        [elementName isEqualToString:kDescriptionElementName] ||
        [elementName isEqualToString:kSummaryElementName] ||
        [elementName isEqualToString:kContentElementName] ||
        [elementName isEqualToString:kPubDateElementName] ||
        [elementName isEqualToString:kDateElementName] ||
        [elementName isEqualToString:kLinkElementName] ||
        [elementName isEqualToString:kGuidElementName] ||
        [elementName isEqualToString:kIdElementName]) {
        accumulatingParsedCharacterData = YES;
        [currentParsedCharacterData setString:@""];
    }
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
                                      namespaceURI:(NSString *)namespaceURI
                                     qualifiedName:(NSString *)qName {
    if ([elementName isEqualToString:kEntryElementName] || [elementName isEqualToString:kItemElementName]) {
        if ([self.oldDate compare:self.currentFeedItem.updated] < 0) {
            [self.currentParseBatch addObject:self.currentFeedItem];
            parsedItemsCounter++;
            if ([self.currentParseBatch count] >= kSizeOfEarthquakeBatch) {
                [self saveCurrentBatch];
                self.currentParseBatch = [NSMutableArray array];
            }
        }
    } else if ([elementName isEqualToString:kTitleElementName]) {
        self.currentFeedItem.Title = [[self.currentParsedCharacterData copy] autorelease];
    } else if ([elementName isEqualToString:kDescriptionElementName] || [elementName isEqualToString:kSummaryElementName] || [elementName isEqualToString:kContentElementName]) {
        self.currentFeedItem.Description = [[self.currentParsedCharacterData copy] autorelease];
    } else if ([elementName isEqualToString:kUpdatedElementName] || [elementName isEqualToString:kPubDateElementName] || [elementName isEqualToString:kDateElementName]) {
        if (self.currentFeedItem != nil) {
            NSString *parseErrors = nil;
            NSDate *newDate = nil;
            if ([dateFormatter1 getObjectValue:&newDate forString:self.currentParsedCharacterData errorDescription:&parseErrors]) {
                self.currentFeedItem.Updated = newDate;
            } else if ([dateFormatter2 getObjectValue:&newDate forString:self.currentParsedCharacterData errorDescription:&parseErrors]) {
                self.currentFeedItem.Updated = newDate;
            }
        }
    } else if ([elementName isEqualToString:kLinkElementName]) {
        if ([self.currentParsedCharacterData length] > 0) {
            self.currentFeedItem.Link = [[self.currentParsedCharacterData copy] autorelease];
        }
    } else if ([elementName isEqualToString:kGuidElementName] || [elementName isEqualToString:kIdElementName]) {
        self.currentFeedItem.guid = [[self.currentParsedCharacterData copy] autorelease];
    }

    accumulatingParsedCharacterData = NO;
}

// This method is called by the parser when it find parsed character data ("PCDATA") in an element.
// The parser is not guaranteed to deliver all of the parsed character data for an element in a single
// invocation, so it is necessary to accumulate character data until the end of the element is reached.
//
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    if (accumulatingParsedCharacterData) {
        [self.currentParsedCharacterData appendString:string];
    }
}

- (void)dealloc {
    [feedData release];
    
    [currentFeedItem release];
    [currentParsedCharacterData release];
    [dateFormatter1 release];
    [dateFormatter2 release];
    
    [super dealloc];
}


@end
